require 'digest/sha1'

class Cartasur::Track < Track
  establish_connection "cartasur_#{Rails.env}"

  belongs_to :user, :class_name => 'Cartasur::User'
  belongs_to :protocol, :class_name => 'Cartasur::Protocol'
  has_many :measures, :class_name => 'Cartasur::Measure'

  attr_accessible :user_id


  LEAST_MEASURES_SIZE_PER_TRACK = 10

  ##-------------- newer
 def user_name
   Cartasur::User.find(user_id).login
 end
  def track_details
    "Created at: " << self.created_at.to_s << " | Creator: " << user_name
  end


  # ---- overridden ----------
  def add_measurement(param)
    Cartasur::Measure.save_request(param, self, self.user)
  end

  def popular_track_tags
    Cartasur::MeasurementsTag.select("tag_name, count(tag_name) as count")
    .where("track_id = #{self.id}").group("tag_name")
    .order("count(tag_name) desc").limit(5)
    #select tag_name, count(tag_name) from measurements_tags
    #where user_id =  52 group by tag_name
    #order by count(tag_name) desc
  end


  # Create a track from a xml
  def self.from_xml (xml_doc, user, protocol, constraints)
    # get the rootnode
    root = REXML::XPath.first( xml_doc, "//NoiseTube-Mobile-Session")

    # TODO if attribute "userKey" is present on the rootNode it's contents
    # should be matched with the API key of the current user

    # make a new track and set attributes...
    track = Cartasur::Track.new(:user=>user)
    track.starts_at = DateTime.parse(root.attribute("startTime").value)
    track.client = root.attribute("client").value  #TODO do a lookup/insert in the clients table and set the client_id field instead of using this string field
    track.client_version = root.attribute("clientVersion").value  unless root.attribute("clientVersion").nil?

    track.device = "" << root.attribute('deviceModel').try(:value) << ' ' << root.attribute('devicePlatform').try(:value) << '. Client: ' << root.attribute("clientVersion").try(:value)

    device_model = root.attribute("deviceJ2MEPlatform").try(:value)

    #need to quit if device is not HTC One X
    if !device_model.nil? and device_model.gsub(/\s+/, "").downcase  != "htconex"
      raise "Measurements not recorded in the recommended phone model"
    end

    track.track_comment = "Uploaded through website. Calibration: " <<  root.attribute("calibration").try(:value)
    track.save

    # process all the measurements - build sql is faster than build objects
    #  xml example: <measurement timeStamp="2009-05-14T19:14:50+02:00" loudness="21" location="" tags="car"/>
    logger.info "----- New track by uploding data ------"
    puts "Cartasur: New track by uploding data"
    measures_insert_sql_start = "INSERT INTO measures(track_id,geom,loudness,loudness_index,is_objective_gps,created_at,made_at) VALUES "
    measures_insert_sql_values = []

    tags_insert_sql_start = "INSERT INTO measurements_tags(track_id,tag_name,user_id,context,begin_id,end_id,created_at) VALUES "
    tags_insert_sql_values = []
    # count location missing in phones without GIS
    missing_locations_count = 0
    #save the measurements
    REXML::XPath.each( xml_doc, "//measurement" ) { |measurement|

      params={}
      params[:time] = measurement.attribute("timeStamp").value #is being parsed in measure.save_request
      params[:db]  = measurement.attribute("loudness").value.to_f if !measurement.attribute("loudness").nil?
      params[:l]  = measurement.attribute("location").value if !measurement.attribute("location").nil?
      params[:tag]  = measurement.attribute("tags").value if !measurement.attribute("tags").nil?

      # 28/05/2013
      #the location - if no location, then use the objectives location for constraints = 2
      is_gps_from_objective = false
      loctext =  Cartasur::Measure.wkt_from_loc(params[:l])
      if loctext.blank? #if no location
        missing_locations_count = missing_locations_count + 1

        if constraints == "2" #look for gps from objective location

          location = Cartasur::Location.find(protocol.objective.try(:location))

          unless location.nil?
            #loctext = "'#{location.coordinates.try(:as_text)}'"
            #fix for reversed lat long vals
            loctext = " 'POINT(#{location.coordinates.try(:y)} #{location.coordinates.try(:x)})' "
            is_gps_from_objective = true
          end

        end unless protocol.nil?

      else #loctext is not blank
        loctext = "'#{loctext}'"
      end

      #safe measure if still we find no gps
      loctext = "NULL" if loctext.blank?

      #for tags, can be comma-separated..
      if params[:tag].blank?
        #no tags - batch the save of the measure
        measures_insert_sql_values << "(#{track.id}, #{loctext}, #{params[:db]}, #{SoundLevel.compute_loudness_index(params[:db])},#{is_gps_from_objective}, '#{Time.now}', '#{params[:time]}') "
      else
        #need to save the measurement instead if building the sql - to get the id that links it to the tag
        nm = Cartasur::Measure.new()
        nm.track = track
        nm.geom = loctext.gsub("'", "") #strip quotes
        nm.loudness = params[:db]
        nm.loudness_index = SoundLevel.compute_loudness_index(params[:db])

        #is the gps coordinate derived from objective?
        nm.is_objective_gps = is_gps_from_objective

        nm.created_at = Time.now
        nm.made_at = params[:time]
        nm.user = user # this will be removed
        nm.save!

        the_tags = params[:tag].split(",")
        the_tags.each { |ptag|
          tags_insert_sql_values << "(#{track.id}, #{sanitize(ptag)}, #{track.user_id}, 'tags', #{nm.id}, -1, '#{params[:time]}') " #todo: tag intervals?
        }
      end

      #track.add_measurement(params)
    }
    #now save the measurements as a batch
    Cartasur::Measure.save_as_transactions(measures_insert_sql_start, measures_insert_sql_values) unless measures_insert_sql_values.blank?
    Cartasur::MeasurementsTag.save_tags_as_transactions(tags_insert_sql_start, tags_insert_sql_values) unless tags_insert_sql_values.blank?

    #add the protocol to track
    track.protocol = protocol

    track.ends_at = track.measures.find(:last, :order=>"made_at").try(:made_at)
    track.save
    if missing_locations_count > 10
      logger.warn("Cartasur: Measurements created at #{Time.now} on track #{track.id} contained #{missing_locations_count} measurements without GIS data.")
    end
    return track
  end


  #process from rake task
  def process(email=false)
    measure_count = self.measures.count
    user_login =   Cartasur::User.find(self.user_id).try(:login)
    puts "Cartasur: processing track #{self.id} (#{measure_count} measurements) of #{user_login}..."
    logger.info "Cartasur: processing track #{self.id} (#{measure_count} measurements) of #{user_login}..."

    return if self.process_clean_and_index == true #stop processing if process_clean returns true (track was invalid and is deleted)
    logger.info "Cartasur: process_clean_and_index track #{self.id} done."

    self.process_base
    logger.info "Cartasur: processs_base done."

    #self.process_geo_correction if self.geolocated #disabled for now (needs bug fix in geo_features.rb + map data of other places than paris)
    #logger.info "geo correction done."

    # New version of noisetube does not support autotagging
    #self.process_automatic_tagging
    #logger.info "automatic tagging done."

    self.processed = true
    self.public = self.user.public
    self.measures_size = measure_count
    #city
    self.city = self.user.city

    self.save

    # update last activity attribute
    self.user.last_activity_at=self.ends_at
    self.user.measures_size = self.user.measures_size + measure_count
    self.user.save

    unless self.user.city.nil?
      self.user.city.last_activity_at=self.ends_at
      self.user.city.save
    end

    begin
      CartMailer.delay.deliver_new_track(self) if email
      logger.info "Cartasur: Sent track #{self.id} ready email to #{email}"  if email
    rescue => ex
      logger.error ("Cartasur: Error: Could not send email to #{email}. "<<ex.message)
    end


    logger.info "Cartasur: ..track #{self.id} by user #{user_login} processed."
  end
  # process_clean - removes the invalid measures, and checks if the track has
  # more than 3 measures after that and if so, deletes it
  def process_clean_and_index
    self.measures.each { |m|
      if m.isvalid?
        m.loudness_index=SoundLevel.compute_loudness_index(m.loudness)
        m.time_index=Cartasur::Measure.compute_time_index(m)
        m.save
      else
        #m.destroy    <- Cartasur: No deleting measurements that are not valid
      end
    }
    if self.measures.size < LEAST_MEASURES_SIZE_PER_TRACK && self.user.id != 1395 #for dublin festival (users customised android app with hardcoded account, we expect people to make short tracks)
      logger.warn "Cartasur: WARNING!! Not enough (valid) measures => deleting track #{self.id}"
      self.destroy
      return true
    end
    return false
  end

  def process_base

    # set starts_at to timestamp of first measurement
    self.starts_at = self.measures.first(:order=>"made_at").try(:made_at)

    # set ends_at to timestamp of last measurement
    self.ends_at = self.measures.find(:last, :order=>"made_at").try(:made_at)

    # number of measurements
    self.count = self.measures.size

    # duration
    #self.duration = self.ends_at - self.starts_at

    # avg loudness
    loudness_data=self.measures.collect{ |m| m.loudness }
    self.avgloudness=SoundLevel::compute_decibel(loudness_data)
    min = 1000000.0
    max = 0.0
    loudness_data.each { |leq|
      min = leq if leq < min
      max = leq if leq > max
    }
    self.minloudness = min
    self.maxloudness = max

    # graph computing
    self.graph_image_url=self.to_graph_url

    # location computing
    geodata = []
    # use the geodata to store bounding box and coverage, distance
    self.measures.all(:order=>"made_at").each { |m| geodata << m.geom unless m.geom.nil? }

    unless (geodata.size < 2)
      puts "Track is geolocated"
      self.geolocated = true

      # bounding box
      geom_shape = GeoFeatures::to_box(geodata)
      self.geom=geom_shape
      #self.geom.srid = 4326

      # coverage
      self.distance=GeoFeatures::coverage(geodata)

      # compute image overlay
      # Overlay.build_image(self.measures, "#{RAILS_ROOT}/public/images/tracks/#{self.id}.png")
      # #self.image_relative_url="/images/tracks/#{self.id}.png"
    else
      puts "Track is not geolocated"
      self.geolocated = false #just to be sure (and correct wrongly labeled tracks due to previous buggy postprocessing)
    end

    #find_location # find associated city geographically

    self.save #!!!
  end

  def to_graph_url(options={})

    if self.graph_image_url.nil?
      # no graph url => compute it
      measures = (self.measures.size>2000) ? self.measures[0..1999] : self.measures
      graph = SoundLevel::graph(measures, options)

      # save only if the track has been processed
      if self.processed
        self.graph_image_url=graph
        self.save
      end

      return graph
    else
      return self.graph_image_url
    end
  end

  #get the visited for this track.. if it exists (it shld)
  def visited_for_track
    Cartasur::Visited.where("protocol_id = #{self.protocol.id} and track_id=#{self.id}").try(:first) unless protocol_id.nil?
  end

end
